/* Copyright (c) 2017, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */
#include "NVIDIAGfeSDKPrivatePCH.h"
#include "FNVIDIAGfeSDK.h"
#include "Runtime/Core/Public/Misc/Paths.h"
#include "Runtime/Core/Public/Windows/WindowsPlatformProcess.h"
#include "IPluginManager.h"
#include "gfesdk/sdk_types.h"


#define LOCTEXT_NAMESPACE "NVIDIAGfeSDKPlugin"
#define PLUGIN_VERSION "1.0.0"

DEFINE_LOG_CATEGORY(LogGfeSDK);

static void __stdcall LoggingCallback(GfeSDK::NVGSDK_LogLevel level, char const* text)
{
    switch (level)
    {
    case GfeSDK::NVGSDK_LOG_NONE:
        break;
    case GfeSDK::NVGSDK_LOG_ERROR:
        UE_LOG(LogGfeSDK, Warning, TEXT("%s"), UTF8_TO_TCHAR(text));
        break;
    case GfeSDK::NVGSDK_LOG_INFO:
    case GfeSDK::NVGSDK_LOG_DEBUG:
    case GfeSDK::NVGSDK_LOG_VERBOSE:
    default:
        UE_LOG(LogGfeSDK, Log, TEXT("%s"), UTF8_TO_TCHAR(text));
        break;
    }
}

void FNVIDIAGfeSDK::StartupModule()
{
#if PLATFORM_WINDOWS
#if _WIN64
    FString WinDir = TEXT("Win64/");
#else
    FString WinDir = TEXT("Win32/");
#endif
#else
#error Platform currently unsupported
#endif // PLATFORM_WINDOWS
	
    // Get the base directory of this plugin
	const FString PluginPath = IPluginManager::Get().FindPlugin(TEXT("NVIDIAGfeSDK"))->GetBaseDir();

    FString RootDllPath = PluginPath / FString::Printf(TEXT("ThirdParty/NVIDIAGfeSDK/redist/")) / WinDir;

    FPlatformProcess::PushDllDirectory(*RootDllPath);
    LibHandle = FPlatformProcess::GetDllHandle(*(RootDllPath + "GfeSDK.dll"));
    FPlatformProcess::PopDllDirectory(*RootDllPath);

    if (!LibHandle)
    {
        UE_LOG(LogTemp, Warning, TEXT("Failed to load GfeSDK library."));
    }
    else
    {
        UE_LOG(LogTemp, Log, TEXT("Using NVIDIAGfeSDKPlugin version %s"), TEXT(PLUGIN_VERSION));
        GfeSDK::AttachLogListener(LoggingCallback);
    }
}

void FNVIDIAGfeSDK::ShutdownModule()
{

    Release();
    if (LibHandle)
    {
        FPlatformProcess::FreeDllHandle(LibHandle);
        LibHandle = nullptr;
    }
}

static GfeSDK::NVGSDK_LogLevel getLogLevel(ELogVerbosity::Type verbosity)
{
    switch (verbosity)
    {
    case ELogVerbosity::NoLogging:
        return GfeSDK::NVGSDK_LOG_NONE;
    case ELogVerbosity::Fatal:
    case ELogVerbosity::Error:
    case ELogVerbosity::Warning:
        return GfeSDK::NVGSDK_LOG_ERROR;
    case ELogVerbosity::Display:
        return GfeSDK::NVGSDK_LOG_INFO;
    case ELogVerbosity::Log:
        return GfeSDK::NVGSDK_LOG_DEBUG;
    case ELogVerbosity::Verbose:
    case ELogVerbosity::VeryVerbose:
        return GfeSDK::NVGSDK_LOG_VERBOSE;
    default:
        return GfeSDK::NVGSDK_LOG_DEBUG;
    }
}

// Controls the log level of GfeSDK's internal logfile
GfeSDK::NVGSDK_RetCode FNVIDIAGfeSDK::SetFileLogLevel(ELogVerbosity::Type Level)
{
    if (!LibHandle) return GfeSDK::NVGSDK_ERR_MODULE_NOT_LOADED;
    return GfeSDK::SetFileLogLevel(getLogLevel(Level));
}

// Controls the log level of logs that GfeSDK sends to the unreal logfile
GfeSDK::NVGSDK_RetCode FNVIDIAGfeSDK::SetUnrealLogLevel(ELogVerbosity::Type Level)
{
    if (!LibHandle) return GfeSDK::NVGSDK_ERR_MODULE_NOT_LOADED;
    return GfeSDK::SetListenerLogLevel(getLogLevel(Level));
}

static const EGfeSDKScope ScopeMap[(size_t)EGfeSDKScope::MAX] = {
	EGfeSDKScope::Highlights,
	EGfeSDKScope::HighlightsRecordVideo,
	EGfeSDKScope::HighlightsRecordScreenshot
};

static const EGfeSDKPermission PermissionMap[] = {
	EGfeSDKPermission::Granted,
	EGfeSDKPermission::MustAsk,
	EGfeSDKPermission::Denied,
	EGfeSDKPermission::Unknown
};

void __stdcall OnNotificationCallback(GfeSDK::NVGSDK_NotificationType type, GfeSDK::NVGSDK_Notification const* response, void* context)
{
	FNVIDIAGfeSDK* SDKObject = static_cast<FNVIDIAGfeSDK*>(context);
	switch (type)
	{
	case GfeSDK::NVGSDK_NOTIFICATION_PERMISSIONS_CHANGED:
	{
		FGfeSDKPermissionsChangedData PermissionsChangedData;

		for (size_t i = 0; i < response->permissionsChanged.scopePermissionTableSize; i++)
		{
			PermissionsChangedData.ScopePermissions.Add(FGfeSDKPairScopePermission(ScopeMap[response->permissionsChanged.scopePermissionTable[i].scope], PermissionMap[response->permissionsChanged.scopePermissionTable[i].permission]));
		}
		if (SDKObject)
			SDKObject->PermissionsChangedDelegate.ExecuteIfBound(PermissionsChangedData);
	}
	}
}

EGfeSDKReturnCode FNVIDIAGfeSDK::Create(FGfeSDKCreateInputParams const& InputParams, FGfeSDKCreateResponse& Response)
{
	if (!GFEHandle)
	{
		GfeSDK::NVGSDK_HANDLE* Handle = nullptr;

		GfeSDK::NVGSDK_CreateInputParams InputParamsCreate;
		memset(&InputParamsCreate, 0, sizeof(InputParamsCreate));
		GfeSDK::NVGSDK_CreateResponse OutputParamsCreate;

		std::string appNameUTF = TCHAR_TO_UTF8(*InputParams.AppName);

		InputParamsCreate.appName = appNameUTF.c_str();
		InputParamsCreate.pollForCallbacks = InputParams.PollForCallbacks;

		std::vector<GfeSDK::NVGSDK_Scope> Scopes(InputParams.RequiredScopes.Num());

		for (size_t i = 0; i<Scopes.size(); i++)
		{
			Scopes[i] = (GfeSDK::NVGSDK_Scope)InputParams.RequiredScopes[i];
		}
		InputParamsCreate.scopeTable = Scopes.data();
		InputParamsCreate.scopeTableSize = Scopes.size();

		InputParamsCreate.notificationCallback = &OnNotificationCallback;
		InputParamsCreate.notificationCallbackContext = this;

		std::vector<GfeSDK::NVGSDK_ScopePermission> cScopePermissionList(Scopes.size());
		OutputParamsCreate.scopePermissionTable = &cScopePermissionList[0];
		OutputParamsCreate.scopePermissionTableSize = Scopes.size();

		GfeSDK::NVGSDK_RetCode Result = GfeSDK::NVGSDK_Create(&Handle, &InputParamsCreate, &OutputParamsCreate);

		if (GfeSDK::NVGSDK_SUCCEEDED(Result))
		{
			GFEHandle = Handle;
			Response.VersionMajor = OutputParamsCreate.versionMajor;
			Response.VersionMinor = OutputParamsCreate.versionMinor;
			Response.NVIDIAGfeVersion = FString(OutputParamsCreate.gfeVersionStr);

			SdkCore.Reset(new FGfeSDKCore(GFEHandle));
			SdkHighlights.Reset(new FGfeSDKHighlights(GFEHandle));

			for (size_t i = 0; i < OutputParamsCreate.scopePermissionTableSize; i++)
			{
				Response.ScopePermissions.Add(FGfeSDKPairScopePermission(
					TranslateEnum(kScopeSdkToUnreal, OutputParamsCreate.scopePermissionTable[i].scope, EGfeSDKScope::MAX),
					TranslateEnum(kPermissionSdkToUnreal, OutputParamsCreate.scopePermissionTable[i].permission, EGfeSDKPermission::MAX)
					));
			}

			// Save it for future when GFEHandle exists.
			SdkResponseRecord = Response;
		}
		else
		{
			// clear the response
			Response = FGfeSDKCreateResponse();
			UE_LOG(LogGfeSDK, Error, TEXT("Failed to create GfeSDK: %d"), (int)Result);
		}

		return TranslateReturnCodeToUnreal(Result);
	}
	else
	{
		// Use saved value.
		Response = SdkResponseRecord;

		return TranslateReturnCodeToUnreal(GfeSDK::NVGSDK_RetCode::NVGSDK_SUCCESS);
	}

}

void FNVIDIAGfeSDK::Release()
{
	if (GFEHandle)
	{
		NVGSDK_Release(GFEHandle);
		GFEHandle = nullptr;
		SdkResponseRecord = FGfeSDKCreateResponse();
	}
    SdkHighlights.Reset();
}

FGfeSDKCore* FNVIDIAGfeSDK::Core()
{
    return SdkCore.Get();
}

FGfeSDKHighlights* FNVIDIAGfeSDK::Highlights()
{
    return SdkHighlights.Get();
}

IMPLEMENT_MODULE(FNVIDIAGfeSDK, NVIDIAGfeSDK)

#undef LOCTEXT_NAMESPACE
